package org.vossnet.sfig;

import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;
import org.antlr.runtime.tree.TreeNodeStream;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.CharStream;

import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.*;

import static java.lang.String.format;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
import org.apache.log4j.Priority;


/**
 * The SFig parser is utilized via this class. Can instantiate instances of RunFig,
 * passing its constructors input sources to parse bean definitions from, or use
 * its loadConfig() methods. The loadConfig will close any previously existing bean
 * definitions context, so can be invoked multiple times.
 * 
 * <p>The parse context of bean definitions should be cleaned up via the close()
 * method when no longer needed for use.
 * 
 * <p>A shutdown hook is registered to insure close() gets called if the JVM
 * is exited from due to some unanticipated means of exiting.
 * 
 * @see #loadConfig(ClassPathResource)
 * @see #loadConfig(String)
 * @see #close()
 *
 * @author rogerv
 */
public final class RunFig {
    private   static final Logger log;
    protected static final boolean useSpringFramework;
    private   static final BeanDefSrvcs beanDefSrvcs;
    /**
     * The static initializer sets a logger, a flag variable to indicate if spring-framework is
     * targeted as the bean factory (otherwise will use the default intrinsic bean factory), and
     * thereby appropriately initializes the BeanDefSrvcs interface reference.
     */
    static {
        Class<?> cls = RunFig.class;
        log = Logger.getLogger(cls);
        if (LogManager.exists(cls.getPackage().getName()) == null)
            setLoggingLevel(Level.WARN);
        String beanDefSrvcsClsName = "/" + cls.getPackage().getName().replace('.', '/') + "/SpringBeanDefServices.class";
        useSpringFramework = cls.getResource(beanDefSrvcsClsName) != null;
        beanDefSrvcsClsName = cls.getPackage().getName() + (useSpringFramework ? ".SpringBeanDefServices" : ".RunFig$DefaultBeanDefServices");
        Object srvcs = null;
        try {
            cls = Class.forName(beanDefSrvcsClsName);
            final Constructor<?> ctor = cls.getConstructor(Logger.class);
            srvcs = ctor.newInstance(log);
        } catch (Exception e) {
            throw new RuntimeException(format("Failed loading class \"%s\":",beanDefSrvcsClsName),e);
        }
        beanDefSrvcs = (BeanDefSrvcs) srvcs;
    }
    protected static void setLoggingLevel(Level level) {
        Logger logger = LogManager.getRootLogger();
        logger.setLevel(level);
        logger = Logger.getLogger(RunFig.class.getPackage().getName());
        logger.setLevel(level);
    }

    /**
     * Instance field that is set to the output of the first-pass
     * lexical and grammatical parser. Is used as the input to the
     * bean factory, and the second-pass tree grammar when an object
     * instance is requested.
     */
    protected final AtomicReference<SFigParser.file_return> beanDefsContext = new AtomicReference<SFigParser.file_return>(null);
    protected final ReentrantLock closeLock = new ReentrantLock();
    protected Thread shutdownHook = null;

    /**
     * Anonymous object initializer that registers a shutdown hook.
     * 
     * <p>Is used to insure the close() method gets invoked for
     * cleaning up the context of the bean definitions.
     * 
     * @see #close()
     */
    {
        shutdownHook = new Thread(new Runnable(){
            @Override public void run() {
                RunFig.this.close();
            }}
        );
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }

    /**
     * Default constructor for a RunFig object instance. Must
     * use one of the loadConfig() methods to ready the RunFig
     * object for use as an object factory.
     * 
     * @see #loadConfig(ClassPathResource)
     * @see #loadConfig(String)
     */
    public RunFig() {}
    /**
     * Construct a RunFig object instance from a Spring Framework
     * ClassPathResource input source. Example would be:
     * 
     *  "classpath:applicationContext.sfig"
     * 
     * @param rsrc Spring ClassPathResource input source
     * @throws IOException
     * @throws RecognitionException
     */
    public RunFig(final ClassPathResource rsrc) throws IOException, RecognitionException {
        try {
            loadConfig(new ANTLRClsPathRsrcStream(rsrc));
        } catch (CloseInProgressException e) {
            // Can silently swallow this exception as it will never occur
            // when loadConfig() is invoked during object construction
        }
    }
    /**
     * Construct a RunFig object from a SFig-format config file context.
     * @param path file name of input source, i.e.: "applicationContext.sfig"
     * @throws IOException
     * @throws RecognitionException
     */
    public RunFig(final String path) throws IOException, RecognitionException {
        try {
            loadConfig(getStreamFromPath(path));
        } catch (CloseInProgressException e) {
            // Can silently swallow this exception as it will never occur
            // when loadConfig() is invoked during object construction
        }
    }

    /**
     * Load a RunFig object instance from a SFig-format config file context.
     * 
     * <p>Any previous bean definitions context will be closed, so loadConfig()
     * can be invoked multiple times.
     * 
     * @param rsrc a Spring-Framework ClassPathResource input source 
     * @throws CloseInProgressException 
     * @throws IOException
     * @throws RecognitionException
     */
    public void loadConfig(final ClassPathResource rsrc) throws CloseInProgressException, IOException, RecognitionException {
        loadConfig(new ANTLRClsPathRsrcStream(rsrc));
    }

    /**
     * Load a RunFig object instance from a SFig-format config file context.
     * 
     * <p>Any previous bean definitions context will be closed, so loadConfig()
     * can be invoked multiple times.
     * 
     * @param path file name of input source, i.e.: "applicationContext.sfig" 
     * @throws CloseInProgressException 
     * @throws IOException
     * @throws RecognitionException
     */
    public void loadConfig(final String path) throws CloseInProgressException, IOException, RecognitionException {
        loadConfig(getStreamFromPath(path));
    }

    /**
     * loadConfig() helper method - is the core implementation.
     * 
     * @see #loadConfig(ClassPathResource)
     * @see #loadConfig(String)
     * 
     * @param inputSrc input source as ANTLR CharStream
     * @throws CloseInProgressException
     * @throws IOException
     * @throws RecognitionException
     */
    protected void loadConfig(CharStream inputSrc) throws CloseInProgressException, IOException, RecognitionException {
        if (!closeLock.tryLock())
            throw new CloseInProgressException();
        try {
            SFigParser.file_return rtn = parseStream(inputSrc);
            inputSrc = null; // make object available now for garbage collection
            rtn = this.beanDefsContext.getAndSet(rtn);
            if (rtn != null)
                closeHelper(rtn.beanDefsMap);
        } finally {
            closeLock.unlock();
        }
    }

    /**
     * Returns an ANTLR CharStream based on either a file or class loader resource.
     * 
     * @param path specifies path name of input.
     * @return ANTLR CharStream
     * @throws IOException
     */
    protected static CharStream getStreamFromPath(final String path) throws IOException {
        return path.toLowerCase().startsWith("classpath:") ?
                    new ANTLRClsPathRsrcStream(new ClassPathResource(path.substring(path.indexOf(':') + 1)))
                :   new ANTLRFileStream(path);
    }

    /**
     * Helper class that is an ANTLR parser CharStream compatible wrapper
     * around a Spring Framework ClassPathResource input source.
     */
    protected static final class ANTLRClsPathRsrcStream extends ANTLRStringStream {
        public ANTLRClsPathRsrcStream(final ClassPathResource rsrc) throws IOException
        {
             final File f = rsrc.getFile();
             final int size = (int)f.length();
             final InputStreamReader isr = new InputStreamReader(new FileInputStream(f));
             try {
                   data = new char[size];
                   super.n = isr.read(data);
             }
             finally {
                   isr.close();
             }
        }
    }

    /**
     * Cycles through the bean factory and invokes the destroy method on any
     * instantiated singleton object instance referenced from the factory.
     * 
     * This method is intended to be called when the application is exiting.
     */
    public void close() {
        Map<String,BeanDef> beansMap = null;
        synchronized (this) {
            final SFigParser.file_return rtn = this.beanDefsContext.getAndSet(null);
            if (rtn == null) return; // nothing to do
            beansMap = rtn.beanDefsMap;
            if (shutdownHook.getId() != Thread.currentThread().getId()) {
                Runtime.getRuntime().removeShutdownHook(shutdownHook);
                shutdownHook = null;
            }
        }
        closeHelper(beansMap);
    }
    protected void closeHelper(final Map<String,BeanDef> beansMap) {
        try {
            closeHelperWithLock(beansMap);
        } catch (InterruptedException e) {
            log.warn(format("%s: close() operation attempt interrupted and did not complete",e.getClass().getSimpleName()));
        } catch (TimeoutException e) {
            log.warn(format("%s: close() operation failed to acquire lock and did not complete",e.getClass().getSimpleName()));
        }
    }
    protected void closeHelperWithLock(final Map<String,BeanDef> beansMap) throws InterruptedException, TimeoutException {
        if (!closeLock.tryLock(7000,java.util.concurrent.TimeUnit.MILLISECONDS))
            throw new TimeoutException();
        try {
            final Collection<BeanDef> beans = beansMap.values();
            for (final BeanDef beanDef : beans) {
                if (!beanDef.isSingleton()) continue;
                final Object bean = beanDef.getBeanInstance();
                if (bean == null) continue;
                try {
                    beanDef.setBeanInstance(null);
                    final Method destroyMeth = beanDef.getDestroyMethod(bean);
                    if (destroyMeth != null)
                        destroyMeth.invoke(bean, (Object[]) null);
                } catch (Exception e) {
                    log.error(format("%s: close() operation encountered problem invoking bean '%s' destroy method:",
                            e.getClass().getSimpleName(), beanDef.getBeanID()), e);
                }
    
            }
        } finally {
            closeLock.unlock();
        }
    }

    /**
     * This exception is thrown when a RunFig public method is invoked
     * when a close() operation is currently in progress (due to another
     * thread execution context - such as the shutdown hook has been
     * invoked).
     * 
     * @author rogerv
     */
    @SuppressWarnings("serial")
    public static final class CloseInProgressException extends Exception {
        public CloseInProgressException() {
            super("close() operation currently in progress");
        }
    }

    /**
     * Bean factory method that returns an instantiated bean by type.
     * 
     * @param cls class type of requested bean
     * @param beanName bean identifier
     * @return instantiated bean
     * @throws Exception if bean definition context uninitialized
     * @throws CloseInProgressException
     * @throws RecognitionException 
     * @throws NoSuchMethodException 
     * @throws SecurityException 
     * @throws InvocationTargetException 
     * @throws IllegalAccessException 
     * @throws IllegalArgumentException 
     */
    public <T> T getBean(final Class<T> cls,final String beanName) throws Exception {
        return cls.cast(getBean(beanName));
    }

    /**
     * Bean factory method that returns an instantiated bean as Object type.
     * 
     * @param beanName bean identifier
     * @return instantiated bean
     * @throws Exception if bean definition context uninitialized
     * @throws CloseInProgressException
     * @throws RecognitionException 
     * @throws NoSuchMethodException 
     * @throws SecurityException 
     * @throws InvocationTargetException 
     * @throws IllegalAccessException 
     * @throws IllegalArgumentException 
     */
    public Object getBean(final String beanName) throws Exception {
        final SFigParser.file_return rtn = this.beanDefsContext.get();
        if (rtn == null)
            throw new Exception("Bean definition context currently not initialized");

        if (!closeLock.tryLock())
            throw new CloseInProgressException();

        Object bean = null;
        try {
            final Map<String,BeanDef> lookup = rtn.beanDefsMap;
            if (lookup.containsKey(beanName)) {
                final BeanDef beanDef = lookup.get(beanName);
                bean = beanDef.isSingleton() ? beanDef.getBeanInstance() : null;
                if (bean == null) {
                    final CommonTree t = beanDef.getAst_node();
                    if (t != null) {
                        final SFigEval walker = new SFigEval(new CommonTreeNodeStream(t));
                        walker.properties = rtn.includeProperties;
                        walker.instances = rtn.preMadeBeansMap;
                        walker.beanDefsMap = rtn.beanDefsMap;
                        bean = walker.beanDef();
                        if (bean != null) {
                            final Method initMeth = beanDef.getInitMethod(bean);
                            if (initMeth != null)
                                initMeth.invoke(bean,(Object[])null);
                            if (beanDef.isSingleton())
                                beanDef.setBeanInstance(bean);
                        }
                    }
                }
                if (bean != null) {
                    final Method factoryMeth = beanDef.getFactoryMethod(bean);
                    if (factoryMeth != null)
                        bean = factoryMeth.invoke(bean,(Object[])null);
                }
            }
        } finally {
            closeLock.unlock();
        }
        return bean;
    }

    /**
     * Use to parse SFig file(s) from command line. Processes configuration, dumps parse info,
     * and instantiates each encountered bean definition into Java object instance.
     * 
     * @param args array of file path names of SFig-format config files
     */
    public static void main(String args[]) {
        if (System.getProperty("log4j.level","").equalsIgnoreCase("debug"))
            setLoggingLevel(Level.DEBUG);
        if (args.length > 0) {
            for (final String path : args) {
                try {
                    final CharStream stream = getStreamFromPath(path);
                    final SFigParser.file_return rtn = parseStream(stream);
                    if (rtn != null)
                        testSFigParseStream(rtn);
                    else {
                        log.error(format(
                                        "SFig source \"%s\" did not return valid parse information",
                                        path));
                    }
                } catch (RecognitionException e) {
                    log.error(format( "%s: encountered parsing input from \"%s\":",
                                        e.getClass().getSimpleName(), path),e);
                } catch (IOException e) {
                    log.error(format( "%s: encountered opening input on \"%s\":",
                                        e.getClass().getSimpleName(), path),e);
                } catch (Exception e) {
                    log.error(format( "%s: encountered unexpected error parsing input from \"%s\":",
                            e.getClass().getSimpleName(), path),e);
                }
            }
        } else {
            System.out.println("Supply one or more SFig file paths on command line");
        }
    }

    /**
     * Test method that can be used to walk the entire AST tree grammar.
     * 
     * <p>Bean definitions are printed out at Debug logging level.
     *  
     * @param rtn input state from the first-pass lexical and grammatical parsing stage.
     * @throws CloseInProgressException 
     * @throws Exception (may be from context of when processing a visited bean definition)
     */
    protected static void testSFigParseStream(final SFigParser.file_return parseRtn) throws Exception {
        forEach(parseRtn,new ProcessOnBeanDef() {
            @Override
            public boolean process(SFigParser.file_return rtn,BeanDef beanDef) throws Exception {
                final String beanDefName = beanDef.getBeanID();
                Object bean = beanDef.getBeanInstance();
                if (bean == null) {
                    final CommonTree t = beanDef.getAst_node();
                    if (t != null) {
                        final TreeNodeStream treeStrm = new CommonTreeNodeStream(t);
                        final SFigEval walker = new SFigEval(treeStrm);
                        walker.properties = rtn.includeProperties;
                        walker.instances = rtn.preMadeBeansMap;
                        walker.beanDefsMap = rtn.beanDefsMap;
                        bean = walker.beanDef(); // returns a new bean instance
                    } else {
                        log.warn(format("Non-instantiated bean definition %s has no AST representation", beanDefName));
                        return true;   // is a warn condition so continue processing any subsequent bean definitions
                    }
                }
                log.debug(format("%nBean instance \"%s\":%n%s%n", beanDefName, bean));
                return true; // successful bean instantiation so continue processing any subsequent bean definitions
            }
        });
    }

    /**
     * Helper method that can be used to iterate each bean definition.
     * 
     * @see ProcessOnBeanDef#process(BeanDef)
     * 
     * @param rtn resulting context of the grammar parsing stage.
     * @param iter call-back visitor that applies desired processing to a bean definition.
     * @throws Exception any call-back generated exceptions will be passed on through
     * (which will result in premature termination of iteration).
     */
    @SuppressWarnings("deprecation")
    protected static void forEach(final SFigParser.file_return rtn, ProcessOnBeanDef iter) throws Exception {
        for (final String beanDefName : rtn.orderedBeanDefs) {
            final BeanDef beanDef = rtn.beanDefsMap.get(beanDefName);
            if (beanDef != null) {
                if (!iter.process(rtn,beanDef)) break;
            } else if (log.isEnabledFor(Priority.WARN)) {
                log.warn(format("Bean definition '%s' not found in lookup map", beanDefName));
            }
        }
    }

    /**
     * Use to iterate each bean definition that has been parsed from SFig configuration.
     * 
     * <p>The call-back that visits a bean definition may terminate iteration prematurely
     * by returning a result of false. 
     * 
     * @see ProcessOnBeanDef#process(BeanDef)
     * 
     * @param iter call-back handler that applies desired processing to a bean definition.
     * @throws CloseInProgressException 
     * @throws Exception any call-back generated exceptions will be passed on through
     * (which will result in premature termination of iteration).
     */
    public void forEach(ProcessOnBeanDef iter) throws Exception {
        final SFigParser.file_return rtn = this.beanDefsContext.get();
        if (rtn == null) return;

        if (!closeLock.tryLock())
            throw new CloseInProgressException();
        try {
            forEach(rtn,iter);
        } finally {
            closeLock.unlock();
        }
    }

    /**
     * Method that does lexical and grammatical parsing of SFig-format input stream.
     * 
     * @param stream ANTLR CharStream input source
     * @return intermediate AST parse tree result and related output.
     * @throws RecognitionException
     */
    protected static SFigParser.file_return parseStream(final CharStream stream) throws RecognitionException {
        // begin parsing and get list of config'd objects
        final SFigParser g = new SFigParser(new CommonTokenStream(new SFigLexer(stream)));
        final SFigParser.file_return rtn = g.file();
        if (log.isDebugEnabled())
            log.debug(format("%nDump of entire parse tree:%n%s%n", rtn.tree.toStringTree()));

        // establish predefined Properties and ArrayList objects
        if (rtn.preMadeBeanDefs.size() > 0) {
            for (final CommonTree t : rtn.preMadeBeanDefs) {
                if (log.isDebugEnabled())
                    log.debug(t.toStringTree());
                final SFigEval walker = new SFigEval(new CommonTreeNodeStream(t));
                walker.properties = rtn.includeProperties;
                walker.beanDefsMap = rtn.beanDefsMap;
                walker.processPreDef();
                final Map.Entry<String, Object> preDefObj = walker.getPreDefObj();
                rtn.preMadeBeansMap.put(preDefObj.getKey(), preDefObj.getValue());
            }
        }
        return rtn;
    }

    protected static <T> T castObject(Class<T> cls,Object o) { return cls.cast(o); }
    
    /**
     * Parser utility method for setting a Java Properties property definition.
     * 
     * <p>If the property string value contains variables of form <i>${foo.bar}</i>,
     * these are replaced by SFig defined property values. (An SFig configuration file
     * may include property definitions; also, Java System property definitions are used
     * in next order of precedence. If undefined, then the variable is left in place.)
     * 
     * <p>If the <i>value</i> argument is an ArrayList object, then the list of
     * items is converted into a comma separated <i>stringized</i> list. That is the
     * value then associated to the property key.
     * 
     * <p>If the <i>value</i> argument is not of type String, then it will be
     * <i>stringized</i> by invoking its toString() method. It's string form will then
     * be set as the property value.
     * 
     * @see java.util.Properties#setProperty(String, String)
     * 
     * @param props the set of properties that the new value will be added to
     * @param key the key associated to be associated to the new value
     * @param value the property value
     * @return previous property value for the specified key, or null if it did not have one
     */
    protected static Object setPopKeyValue(final Properties props,final String key, Object value) {
        if (value instanceof String) {
            value = props.setProperty(key, (String) value);
        } else if (value instanceof ArrayList && !castObject(ArrayList.class,value).isEmpty()) {
            final Pattern regx = Pattern.compile("\\S+");
            final StringBuilder sbuf = new StringBuilder();
            final ArrayList<?> elements = castObject(ArrayList.class,value);
            for (final Object elm : elements) {
                final String s = elm.toString();
                final Matcher m = regx.matcher(s);
                if (m.find())
                    sbuf.append(elm.toString()).append(", ");
                else
                    sbuf.append('\'').append(s).append("', ");
            }
            sbuf.delete(sbuf.lastIndexOf(", "),sbuf.length());
            value = props.setProperty(key,sbuf.toString());
        } else {
            value = props.setProperty(key, value.toString());
        }
        return value;
    }

    /**
     * Helper method used by the tree-grammar parser when resolving from
     * a factory bean definition (and AST node) to an instantiated bean.
     * 
     * <p>This method is used specifically when resolving bean references as
     * it saves the current tree node stream and creates a new one. When
     * done, the original stream is restored.
     */
    protected static Object getBean(final SFigEval walker, final BeanDef beanDef) throws RecognitionException {
        Object bean = beanDef.isSingleton() ? beanDef.getBeanInstance() : null;
        if (bean == null) {
            final TreeNodeStream tSave = walker.getTreeNodeStream();
            try {
                walker.setTreeNodeStream(new CommonTreeNodeStream(beanDef.getAst_node()));
                bean = walker.beanDef();
                if (bean != null && beanDef.isSingleton())
                    beanDef.setBeanInstance(bean);
            } finally {
                walker.setTreeNodeStream(tSave);
            }
        }
        return bean;
    }

    protected static void setObjectProperty(Object o, String propertyName, Object value) {
        beanDefSrvcs.setObjectProperty(o, propertyName, value);
    }

    protected static <T> T newInstance(String className, List<Object> ctorArgs,Class<T> rtnType) {
        return beanDefSrvcs.newInstance(className, ctorArgs, rtnType);
    }

    /**
     * If SFig is <b>not</b> used in conjunction to the Spring-Framework, then
     * this special helper class provides a default implementation of the parser
     * helper methods setObjectProperty() and newInstance(). RunFig then provides
     * a minimal bean factory capability.
     * 
     * <p>The UseSpringFramework annotation must be set to false at compile time
     * when building RunFig in order to select this implementation.
     * 
     * @see RunFig#setObjectProperty(Object, String, Object)
     * @see RunFig#newInstance(String, List)
     * @see UseSpringFramework
     * 
     * @author rogerv
     */
    @SuppressWarnings("unused")
    private static final class DefaultBeanDefServices extends BeanDefServices {
        public DefaultBeanDefServices(Logger logger) { super(logger); }

        @Override
        protected Class<?> getClass(Object o) {
            return o.getClass();
        }

        @Override
        protected void setProperty(String propName, Method m, Object o, Class<?> t, Object value)
            throws IllegalAccessException, InvocationTargetException {
            invokeMethod( m, o, refineValue(t.getName(),value) );
        }

        @Override
        protected void setField(Field f, Object o, Object value) throws IllegalAccessException, NoSuchFieldException {
            f.set(o, refineValue(f.getType().getName(), value));
        }
    }
    
    private static enum PTypes {
        BYTE("byte") {
            @Override public Object getInst(String value) {
                return Byte.valueOf(value);
            }
        },
        CHAR("char") {
            @Override public Object getInst(String value) {
                return Character.valueOf(value.charAt(1));
            }
        },
        SHORT("short") {
            @Override public Object getInst(String value) {
                return Short.valueOf(value);
            }
        },
        INT("int") {
            @Override public Object getInst(String value) {
                return Integer.valueOf(value);
            }
        },
        LONG("long") {
            @Override public Object getInst(String value) {
                return Long.valueOf(value);
            }
        },
        FLOAT("float") {
            @Override public Object getInst(String value) {
                return Float.valueOf(value);
            }
        },
        DOUBLE("double") {
            @Override public Object getInst(String value) {
                return Double.valueOf(value);
            }
        };
        public abstract Object getInst(String value);
        private static final Map<String,PTypes> lookup = new HashMap<String,PTypes>();
        static {
            for(PTypes e : values())
                lookup.put(e.type,e);
        }
        private final String type;
        private PTypes(String type) { this.type = type; }
        public String toString() { return type; }
        public static PTypes get(String type) { return lookup.get(type); }
    }

    /**
     * Method that refines a parse expression to a specific Java type object instance.
     * 
     * @param n parse expression type
     * @param value parse expression literal value
     * @return Java type object instance (or the original literal value if no match)
     */
    protected static Object refineValue(final String n,Object value) {
        final PTypes type = PTypes.get(n);
        return type != null && value instanceof String ? type.getInst((String) value) : value;
    }

    protected static Method getMethod(Class<Object> c, String methodName, Class<Object>[] args) {
        Method m;
        try {
            m = c.getMethod(methodName, args);
        }
        catch (NoSuchMethodException nsme) {
            m = null;
        }
        return m;
    }

    protected static Object invokeMethod(Method m, Object o) throws IllegalAccessException, InvocationTargetException {
        return invokeMethod(m, o,(Object[]) null);
    }

    protected static Object invokeMethod(Method m, Object o, Object... value) throws IllegalAccessException, InvocationTargetException {
        return m.invoke(o, value.length <= 0 || (value.length == 1 && value[0] == null) ? (Object[]) null : value);
    }

    protected static void includeProperties(String path,final Properties props) {
        org.springframework.core.io.Resource r = null;
        if (path.toLowerCase().startsWith("classpath:")) {
            path = path.substring(path.indexOf(':') + 1);
            r = new org.springframework.core.io.ClassPathResource(path);
        } else {
            r = new org.springframework.core.io.FileSystemResource(path);
        }
        try {
            props.load(r.getInputStream());
        } catch (IOException e) {
            log.error(format(   "%s while attempting to include properties from \"%s\":",
                                e.getClass().getSimpleName(),path),e);
        }
    }
}
